﻿#ifndef ___PCLIB_S_SHA256___
#define ___PCLIB_S_SHA256___


#include "../m_core/m_core.hpp"
#include "s_crypto_base.hpp"

#define PCSHA256_LEN 32
#define PCSHA224_LEN 28

static const uint32_t PC_SHA256_K[] =
{
    0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
    0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
    0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
    0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
    0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
    0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
    0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
    0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
    0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
    0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
    0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
    0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
    0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
    0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
    0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
    0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
};

/**
*@brief        SHA256/SHA224计算
*/
class CPCSHA256: CPCNoCopyable
{
public:
    explicit CPCSHA256(bool is224 = false){ Init(is224); }

    /**
    *@brief     直接获取字节或字符串的sha256/sha224
    *@param     src    [in]    输入的数据
    *@param     len    [in]    数据长度
    *@param     is224  [in]    true：SHA224；false：SHA256
    *@return    32/28字节的sha256/sha224
    */
    static std::string GetBytesSHA256(const void* src, size_t len, bool is224 = false)
    {
        CPCSHA256 sha256ctx(is224);
        sha256ctx.Update(src, len);
        return sha256ctx.Finish();
    }

    /**
    *@brief     直接获取字节或字符串的sha256 hmac
    *@param     src[in]         输入的数据
    *@param     srclen[in]      数据长度
    *@param     keybytes[in]    密钥
    *@param     keylen[in]      密钥长度
    *@return    32字节的sha256 hmac
    */
    static std::string GetBytesSHA256HMAC (const void *src, size_t srclen,const void *keybytes, size_t keylen)
    {
        return PCGetBytesHMAC<CPCSHA256>(src,srclen,keybytes,keylen,64,PCSHA256_LEN);
    }


public:
    /**
    * @brief 初始化
    * @param is224  true：SHA224；false：SHA256
    */
    void Init(bool is224 = false)
    {
        /* SHA1 initialization constants */
        memset( m_total, 0, sizeof( m_total ) );
        memset( m_state, 0, sizeof( m_state ) );
        memset( m_buffer, 0, sizeof( m_buffer ) );
        m_is224 = is224;

        if( !m_is224 )
        {
            /* SHA-256 */
            m_state[0] = 0x6A09E667;
            m_state[1] = 0xBB67AE85;
            m_state[2] = 0x3C6EF372;
            m_state[3] = 0xA54FF53A;
            m_state[4] = 0x510E527F;
            m_state[5] = 0x9B05688C;
            m_state[6] = 0x1F83D9AB;
            m_state[7] = 0x5BE0CD19;
        }
        else
        {
            /* SHA-224 */
            m_state[0] = 0xC1059ED8;
            m_state[1] = 0x367CD507;
            m_state[2] = 0x3070DD17;
            m_state[3] = 0xF70E5939;
            m_state[4] = 0xFFC00B31;
            m_state[5] = 0x68581511;
            m_state[6] = 0x64F98FA7;
            m_state[7] = 0xBEFA4FA4;
        }
    }
    
    /**
    * @brief Update
    *
    * @param src    Buffer
    * @param ilen   Length
    */
    void Update(const void *src, size_t ilen )
    {
        if( ilen == 0 || src == NULL)
            return;
        const unsigned char * input = (const unsigned char *) src;
        uint32_t left = m_total[0] & 0x3F;
        size_t fill = 64 - left;

        m_total[0] += (uint32_t) ilen;
        m_total[0] &= 0xFFFFFFFF;

        if( m_total[0] < (uint32_t) ilen )
            m_total[1]++;

        if( left && ilen >= fill )
        {
            memcpy( (void *) (m_buffer + left), input, fill );
            InternalProcess256(  m_buffer ) ;

            input += fill;
            ilen  -= fill;
            left = 0;
        }

        while( ilen >= 64 )
        {
            InternalProcess256(  input ) ;
            input += 64;
            ilen  -= 64;
        }
        if( ilen > 0 )
            memcpy( (void *) (m_buffer + left), input, ilen );
    }

    /**
    * @brief 计算结果
    */
    std::string Finish()
    {
        uint32_t used;
        uint32_t high, low;
        unsigned char output[PCSHA256_LEN] ;

        /*
         * Add padding: 0x80 then 0x00 until 8 bytes remain for the length
         */
        used = m_total[0] & 0x3F;

        m_buffer[used++] = 0x80;

        if( used <= 56 )
        {
            /* Enough room for padding + length in current block */
            memset(m_buffer + used, 0, 56 - used );
        }
        else
        {
            /* We'll need an extra block */
            memset(m_buffer + used, 0, 64 - used );
            InternalProcess256(  m_buffer ) ;
            memset( m_buffer, 0, 56 );
        }

        /*
         * Add message length
         */
        high = ( m_total[0] >> 29 )
             | ( m_total[1] <<  3 );
        low  = ( m_total[0] <<  3 );

        PC_PUT_UINT32_BE( high, m_buffer, 56 );
        PC_PUT_UINT32_BE( low,  m_buffer, 60 );

        InternalProcess256(  m_buffer ) ;

        /*
         * Output final state
         */
        PC_PUT_UINT32_BE( m_state[0], output,  0 );
        PC_PUT_UINT32_BE( m_state[1], output,  4 );
        PC_PUT_UINT32_BE( m_state[2], output,  8 );
        PC_PUT_UINT32_BE( m_state[3], output, 12 );
        PC_PUT_UINT32_BE( m_state[4], output, 16 );
        PC_PUT_UINT32_BE( m_state[5], output, 20 );
        PC_PUT_UINT32_BE( m_state[6], output, 24 );

        if( !m_is224 )
            PC_PUT_UINT32_BE( m_state[7], output, 28 );

        return std::string( (const char*)output, m_is224? PCSHA224_LEN: PCSHA256_LEN);
    }
    

protected:
    /**
    *@brief 内部处理
    *
    */
    void InternalProcess256(const unsigned char data[64] )
    {
    //////////////////////////////////////////////////////////////////
    #define PCSHA256_SHR(x,n) ((x & 0xFFFFFFFF) >> n)
    #define PCSHA256_ROTR(x,n) (PCSHA256_SHR(x,n) | (x << (32 - n)))

    #define PCSHA256_S0(x) (PCSHA256_ROTR(x, 7) ^ PCSHA256_ROTR(x,18) ^  PCSHA256_SHR(x, 3))
    #define PCSHA256_S1(x) (PCSHA256_ROTR(x,17) ^ PCSHA256_ROTR(x,19) ^  PCSHA256_SHR(x,10))

    #define PCSHA256_S2(x) (PCSHA256_ROTR(x, 2) ^ PCSHA256_ROTR(x,13) ^ PCSHA256_ROTR(x,22))
    #define PCSHA256_S3(x) (PCSHA256_ROTR(x, 6) ^ PCSHA256_ROTR(x,11) ^ PCSHA256_ROTR(x,25))

    #define PCSHA256_F0(x,y,z) ((x & y) | (z & (x | y)))
    #define PCSHA256_F1(x,y,z) (z ^ (x & (y ^ z)))

    #define PCSHA256_R(t)                                    \
    (                                               \
        W[t] = PCSHA256_S1(W[t -  2]) + W[t -  7] +          \
               PCSHA256_S0(W[t - 15]) + W[t - 16]            \
    )

    #define PCSHA256_P(a,b,c,d,e,f,g,h,x,K)                  \
    {                                               \
        temp1 = h + PCSHA256_S3(e) + PCSHA256_F1(e,f,g) + K + x;      \
        temp2 = PCSHA256_S2(a) + PCSHA256_F0(a,b,c);                  \
        d += temp1; h = temp1 + temp2;              \
    }
    //////////////////////////////////////////////////////////////////

        uint32_t temp1, temp2, W[64];
        uint32_t A[8];
        unsigned int i;

        for( i = 0; i < 8; i++ )
            A[i] = m_state[i];


        for( i = 0; i < 16; i++ )
            PC_GET_UINT32_BE( W[i], data, 4 * i );

        for( i = 0; i < 16; i += 8 )
        {
            PCSHA256_P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i+0], PC_SHA256_K[i+0] );
            PCSHA256_P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[i+1], PC_SHA256_K[i+1] );
            PCSHA256_P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[i+2], PC_SHA256_K[i+2] );
            PCSHA256_P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], W[i+3], PC_SHA256_K[i+3] );
            PCSHA256_P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], W[i+4], PC_SHA256_K[i+4] );
            PCSHA256_P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], W[i+5], PC_SHA256_K[i+5] );
            PCSHA256_P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], W[i+6], PC_SHA256_K[i+6] );
            PCSHA256_P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], W[i+7], PC_SHA256_K[i+7] );
        }

        for( i = 16; i < 64; i += 8 )
        {
            PCSHA256_P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], PCSHA256_R(i+0), PC_SHA256_K[i+0] );
            PCSHA256_P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], PCSHA256_R(i+1), PC_SHA256_K[i+1] );
            PCSHA256_P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], PCSHA256_R(i+2), PC_SHA256_K[i+2] );
            PCSHA256_P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], PCSHA256_R(i+3), PC_SHA256_K[i+3] );
            PCSHA256_P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], PCSHA256_R(i+4), PC_SHA256_K[i+4] );
            PCSHA256_P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], PCSHA256_R(i+5), PC_SHA256_K[i+5] );
            PCSHA256_P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], PCSHA256_R(i+6), PC_SHA256_K[i+6] );
            PCSHA256_P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], PCSHA256_R(i+7), PC_SHA256_K[i+7] );
        }

        for( i = 0; i < 8; i++ )
            m_state[i] += A[i];
    //////////////////////////////////////////////////////////////////
    #undef PCSHA256_SHR
    #undef PCSHA256_ROTR
    #undef PCSHA256_S0
    #undef PCSHA256_S1
    #undef PCSHA256_S2
    #undef PCSHA256_S3
    #undef PCSHA256_F0
    #undef PCSHA256_F1
    #undef PCSHA256_R
    #undef PCSHA256_P
    //////////////////////////////////////////////////////////////////
    }

    uint32_t m_total[2];          /*!< The number of Bytes processed.  */
    uint32_t m_state[8];          /*!< The intermediate digest state.  */
    unsigned char m_buffer[64];   /*!< The data block being processed. */
    bool m_is224;                  /*!< Determines which function to use:
                                     0: Use SHA-256, or 1: Use SHA-224. */
};





#endif    //___PCLIB_S_SHA256___